package org.zoomdev.zoom.dao.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zoomdev.zoom.common.io.Io;
import org.zoomdev.zoom.dao.Ar;
import org.zoomdev.zoom.dao.ConnectionExecutor;
import org.zoomdev.zoom.dao.Dao;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.DatabaseConfig;
import org.zoomdev.zoom.dao.DatabaseStruct;
import org.zoomdev.zoom.dao.Entity;
import org.zoomdev.zoom.dao.EntityBuilder;
import org.zoomdev.zoom.dao.Record;
import org.zoomdev.zoom.dao.ResultSetParser;
import org.zoomdev.zoom.dao.SqlDriver;
import org.zoomdev.zoom.dao.builder.DatabaseBuilder;
import org.zoomdev.zoom.dao.builder.impl.ZoomDatabaseBuilder;
import org.zoomdev.zoom.dao.driver.SqlDriverFactory;
import org.zoomdev.zoom.dao.entity.EntityBuilderImpl;
import org.zoomdev.zoom.dao.structs.DatabaseStructFactory;
import org.zoomdev.zoom.dao.transaction.Transactions;
import org.zoomdev.zoom.dao.utils.DaoUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class ZoomDao implements Dao {

    private static final Log log = LogFactory.getLog(Dao.class);
    protected final DataSource ds;
    private final int index;
    private final DatabaseStruct databaseStruct;
    private final String url;
    private final SqlDriver sqlDriver;
    private final String productName;

    private final DatabaseConfig databaseConfig;
    private final Map<EntityKey, Entity> entityMap = new ConcurrentHashMap<>();
    private boolean output;
    private DatabaseBuilder builder;


    public ZoomDao(DataSource ds) {
        this.ds = ds;
        this.index = Transactions.register(this);

        Connection connection = null;
        try {
            connection = ds.getConnection();
            DatabaseMetaData metaData = connection.getMetaData();
            String productName = metaData.getDatabaseProductName();
            this.productName = productName;
            this.url = metaData.getURL();
            log.info(String.format("数据库信息 产品名称: %s URL: %s", productName, url));
            this.sqlDriver = SqlDriverFactory.createByProduct(productName);
            String databaseName = sqlDriver.getDatabaseName(url);
            this.databaseStruct = DatabaseStructFactory.create(this, productName, databaseName);
            this.databaseConfig = new DatabaseConfigImpl();
        } catch (SQLException e) {
            throw new DaoException("创建Dao失败,连接数据库错误", e);
        } finally {
            Io.close(connection);
        }
    }

    private Ar getActiveRecord() {
        Transactions trans = Transactions.get();
        Ar record = trans.getActiveRecord(index);
        if (record == null) {
            record = trans.setActiveRecord(index, new ArImpl(this, trans));
        }
        return record;
    }

    public DataSource dataSource() {
        return ds;
    }

    @Override
    public Ar<Record> ar() {
        return null;
    }

    @Override
    public Ar<Record> ar(String table) {
        return getActiveRecord().entity(entity(Record.class, table));
    }

    @Override
    public <T> Ar<T> ar(Class<T> type) {
        return null;
    }

    public DatabaseConfig config() {
        return databaseConfig;
    }

    public Entity entity(Class<?> entityClass) {
        return entity(new EntityKey(entityClass));
    }

    public Entity entity(Class<?> entityClass, String tableName) {
        return entity(new EntityKey(entityClass, tableName));
    }

    public SqlDriver sqlDriver() {
        return sqlDriver;
    }

    public String productName() {
        return productName;
    }

    public Entity entity(EntityKey entityKey) {
        return entityMap.computeIfAbsent(entityKey, k -> entityBuilder().setType(k.type).setTable(k.tables).build());
    }


    private EntityBuilder entityBuilder() {
        return new EntityBuilderImpl(this, databaseStruct, databaseConfig);
    }


    public <T> T execute(ConnectionExecutor executor) {
        Transactions transactions = Transactions.get();
        return transactions.execute(ds, executor);
    }


    public <T> T executeQuery(ResultSetParser parser, String sql, Object... args) {
        return execute(DaoUtils.getQueryExecutor(parser, sql, args));
    }

    @Override
    public int executeUpdate(String sql, Object... args) {
        return execute(connection -> DaoUtils.executeUpdate(connection, sql, args));
    }

    public void setOutput(boolean output) {
        this.output = output;
    }

    @Override
    public DatabaseBuilder builder() {
        if (builder == null) {
            builder = new ZoomDatabaseBuilder(this);
        }
        return builder;
    }
}
